import logging

import docker
from docker.errors import NotFound
from flask import Blueprint, g, request
from sqlalchemy import func, or_
from sqlalchemy.exc import IntegrityError

from app.core.api import api_fail, api_success
from app.core.decorators import user_required
from app.core.tools import model2dict
from app.extensions import db
from app.models.admin import Config
from app.models.docker import DockerResource, DockerRunner
from app.tasks.vulnerability import start_vuln_resource, stop_vulnerability_resource

logger = logging.getLogger(__name__)
bp = Blueprint("user_vuln", __name__, url_prefix="/api/")


@bp.get("/vulnerability")
@user_required(required=False)
def vuln_list():
    """
    漏洞列表
    """
    page = int(request.args.get("page", 1))
    page_size = int(request.args.get("page_size", 100))
    search = request.args.get("search")
    out_ip = Config.get_config(Config.KEY_IP)
    db_query = db.session.query(DockerResource).filter(
        DockerResource.status == DockerResource.STATUS_BUILD, DockerResource.resource_type == "VUL"
    )
    if search:
        db_query = db_query.filter(
            or_(
                DockerResource.name.contains(search)
                | DockerResource.app.contains(search)
                | DockerResource.cve.contains(search)
            )
        )
    if g.user:
        runner = db.session.query(DockerRunner).filter(DockerRunner.user_id == g.user.id)
        rids = {i.resource_id: i for i in runner}
    else:
        rids = {}
    page = db_query.order_by(DockerResource.created_at.desc()).paginate(
        page=page, per_page=page_size
    )
    data = []
    for item in page.items:
        info = model2dict(item)
        info["docker_type_name"] = item.docker_type_name
        info["status_name"] = item.status_name
        rid: DockerRunner = rids.get(item.id)
        if rid:
            info["runner"] = {"id": rid.id, "out_ip": out_ip, "port_info": rid.port_info}
        else:
            info["runner"] = {}
        data.append(info)
    return api_success({"total": page.total, "data": data})


@bp.get("/vulnerability/apps")
def vuln_apps():
    query = (
        db.session.query(DockerResource.app, func.count(DockerResource.id))
        .filter(DockerResource.resource_type == "VUL")
        .group_by(DockerResource.app)
    )
    data = []
    for app, cnt in query.all():
        data.append({"app": app, "cnt": cnt})
    return api_success({"data": data})


@bp.get("/vulnerability/<int:pk>")
def vuln_detail(pk):
    instance = DockerResource.get_by_id(pk)
    return api_success({"data": model2dict(instance)})


@bp.post("/vulnerability/<int:pk>/start")
@user_required()
def vuln_start(pk):
    try:
        rid = start_vuln_resource(pk, user_id=g.user.id)
        if rid:
            etc = Config.get_config(Config.KEY_VULNERABILITY_TIMEOUT)
            logger.info(etc)
            stop_vulnerability_resource.apply_async(args=(rid,), countdown=etc)
    except IntegrityError:
        return api_fail(msg="请勿重复创建环境")
    except ValueError:
        return api_fail(msg="资源启动失败,请联系管理员!")
    return api_success()


@bp.post("/vulnerability/<int:pk>/stop")
@user_required()
def vuln_stop(pk):
    docker_api = Config.get_config(Config.KEY_DOCKER_API)
    instance: DockerRunner = (
        db.session.query(DockerRunner)
        .filter(DockerRunner.user_id == g.user.id, DockerRunner.resource_id == pk)
        .first()
    )
    if not instance:
        return api_success()
    client = docker.DockerClient(docker_api)
    try:
        docker_container = client.containers.get(instance.container_id)
        docker_container.stop()
        docker_container.remove()
    except NotFound:
        pass
    except docker.errors.APIError as e:
        logger.exception(e)
        return api_fail(msg="操作失败，请稍后再试")

    instance.delete()
    return api_success()
